package top.codedance.iotp.converter;

import com.alibaba.fastjson.JSONObject;
import top.codedance.iotp.converter.bean.ComConnectParam;
import top.codedance.iotp.converter.bean.MasterConnectParam;
import top.codedance.iotp.converter.bean.SlaveConnectParam;
import top.codedance.iotp.common.util.CommandCli;
import top.codedance.iotp.common.util.SystemUtil;
import top.codedance.iotp.converter.configuration.CliCmdOptionsConfig;
import top.codedance.iotp.converter.service.master.AbstractMaster;
import top.codedance.iotp.converter.service.master.Master;
import top.codedance.iotp.converter.service.slave.AbstractSlave;
import top.codedance.iotp.converter.service.slave.Slave;
import com.intelligt.modbus.jlibmodbus.Modbus;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.ini4j.Ini;
import org.ini4j.Wini;

import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class ConverterApplication {
    private static CommandCli commandCli; //命令解析工具
    private static Wini ini = null; //ini文件读取器
    private static Logger logger;
    private static ExecutorService pool;
    private static Map<Integer, ComConnectParam> comParams;
    private static Map<Integer, SlaveConnectParam> slaveParams;
    public static Map<Integer, Slave> slaves = new HashMap<>();
    private static MasterConnectParam masterParam;
    public static Map<String, Integer[]> masterDataParams;
    public static Master master;
    private static final String COM_SECTION_NAME = "com";
    private static final String MASTER_SECTION_NAME = "master";
    private static final String SLAVE_SECTION_NAME = "slave";
    private static final String REGISTER_RCOIL_SECTION_NAME = "register-coil";
    public static ExecutorService workerPool = Executors.newCachedThreadPool();

    public static void main(String[] args) {
        commandCli = new CommandCli(args, CliCmdOptionsConfig.getCmds());
        logger = getLogger(ConverterApplication.class);
        logger.setLevel(Level.toLevel(ConverterApplication.commandCli.getArg("logLevel", "ERROR")));

        initIniConfig();
        comParams = getComParams();
        masterDataParams = getMasterDataParams();
        masterParam = getMasterParam();
        slaveParams = getSlavesParam();
        pool = Executors.newFixedThreadPool(slaveParams.size() + 1);
        run();
    }

    private static void run() {
        master = AbstractMaster.getInstance(masterParam);
        pool.submit(master);

        Set<Map.Entry<Integer, SlaveConnectParam>> entries = slaveParams.entrySet();
        for (Map.Entry<Integer, SlaveConnectParam> entry : entries) {
            Slave slave = AbstractSlave.getInstance(entry.getValue());
            pool.submit(slave);
            slaves.put(entry.getKey(), slave);
        }

        try {
            syslogInfo("Server is running!");
            pool.awaitTermination(10, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            logger.info("Thread pool finished, and restart task.");
        } finally {
            if (pool.isShutdown()) {
                try {
                    Thread.sleep(2000);
                    ConverterApplication.syslogInfo("Converter restarting ... ");
                } catch (Exception ex) {
                }
                run();
            }
        }
    }

    /**
     * 讯取所有com参数
     *
     * @return
     */
    private static Map<Integer, ComConnectParam> getComParams() {
        Map<Integer, ComConnectParam> coms = new HashMap<>();
        Map<Integer, JSONObject> slaves = new HashMap<>();
        if (ini != null && ini.containsKey(COM_SECTION_NAME)) {
            Ini.Section section = ini.get(COM_SECTION_NAME);
            Set<Map.Entry<String, String>> entries = section.entrySet();
            String[] keyTmp;
            JSONObject jo;
            Integer key;
            for (Map.Entry<String, String> entry : entries) {
                keyTmp = entry.getKey().split("\\.");
                key = Integer.valueOf(keyTmp[1]);
                if (slaves.containsKey(key)) {
                    jo = slaves.get(key);
                } else {
                    jo = new JSONObject();
                    jo.put("key", key);
                    slaves.put(key, jo);
                }
                jo.put(keyTmp[0], entry.getValue());
            }
            Set<Map.Entry<Integer, JSONObject>> entries1 = slaves.entrySet();
            for (Map.Entry<Integer, JSONObject> entry : entries1) {
                coms.put(entry.getKey(), JSONObject.toJavaObject(entry.getValue(), ComConnectParam.class));
            }
        } else {
            coms = null;
            syslogInfo("Not found com config, programe is shutdown.");
            System.exit(1);
        }
        return coms;
    }

    /**
     * 获取主串口参数
     *
     * @return
     */
    private static MasterConnectParam getMasterParam() {
        MasterConnectParam mp = new MasterConnectParam();
        if (ini != null && ini.containsKey(MASTER_SECTION_NAME)) {
            Ini.Section section = ini.get(MASTER_SECTION_NAME);
            if (section.containsKey("type")) {
                mp.setType(section.get("type"));
            }
            if (mp.isModbus()) {
                if (section.containsKey("id")) {
                    mp.setId(Integer.parseInt(section.get("id")));
                }
            }
            if (mp.isComType()) {
                if (section.containsKey("com")) {
                    Integer comClientId = Integer.valueOf(section.get("com"));
                    mp.setCom(comClientId);
                    mp.setComConnectParam(comParams.get(comClientId));
                }
            }
        } else {
            mp = null;
            syslogInfo("Not found master config, programe is shutdown.");
            System.exit(1);
        }
        return mp;
    }

    /**
     * 获取从串口参数
     *
     * @return
     */
    private static Map<Integer, SlaveConnectParam> getSlavesParam() {
        Map<Integer, JSONObject> slaves = new HashMap<>();
        if (ini != null && ini.containsKey(SLAVE_SECTION_NAME)) {
            Ini.Section section = ini.get(SLAVE_SECTION_NAME);
            Set<Map.Entry<String, String>> entries = section.entrySet();
            String[] keyTmp;
            JSONObject jo;
            Integer key;
            for (Map.Entry<String, String> entry : entries) {
                keyTmp = entry.getKey().split("\\.");
                key = Integer.valueOf(keyTmp[1]);
                if (slaves.containsKey(key)) {
                    jo = slaves.get(key);
                } else {
                    jo = new JSONObject();
                    jo.put("key", key);
                    slaves.put(key, jo);
                }
                jo.put(keyTmp[0], entry.getValue());
            }
        }
        Map<Integer, SlaveConnectParam> results = new HashMap<>();
        if (slaves.size() > 0) {
            Set<Map.Entry<Integer, JSONObject>> entries = slaves.entrySet();
            JSONObject jo;
            SlaveConnectParam scp;
            Integer comClientId;
            for (Map.Entry<Integer, JSONObject> entry : entries) {
                jo = entry.getValue();
                scp = new SlaveConnectParam();
                scp.setId(jo.getIntValue("id"));
                scp.setType(jo.getString("type"));
                if (!masterCalibrationSlave(scp)) {
                    continue;
                }
                if (scp.isComType()) {
                    comClientId = jo.getInteger("com");
                    scp.setCom(comClientId);
                    scp.setComConnectParam(comParams.get(comClientId));
                }
                if (scp.isNetType()) {
                    scp.setPort(jo.getIntValue("port"));
                }
                results.put(entry.getKey(), scp);
            }
        } else {
            results = null;
            syslogInfo("Not found slaves config, programe is shutdown.");
            System.exit(1);
        }
        return results;
    }

    /**
     * 获取master需要采集的数据
     *
     * @return
     */
    private static Map<String, Integer[]> getMasterDataParams() {
        Map<String, Integer[]> result = new HashMap<>();
        if (ini != null && ini.containsKey(REGISTER_RCOIL_SECTION_NAME)) {
            Ini.Section section = ini.get(REGISTER_RCOIL_SECTION_NAME);
            Set<Map.Entry<String, String>> entries = section.entrySet();
            Integer number;
            for (Map.Entry<String, String> entry : entries) {
                List<Integer> tmpList = new ArrayList<>();
                String[] split = entry.getValue().split(",");
                for (String item : split) {
                    if (item.indexOf("-") != -1) {
                        String[] split1 = item.split("-");
                        int start = Integer.valueOf(split1[0]).intValue();
                        int end = Integer.valueOf(split1[1]).intValue();
                        for (int i = start; i <= end; i++) {
                            try {
                                number = Integer.valueOf(i);
                                if (!tmpList.contains(number)) {
                                    tmpList.add(number);
                                }
                            } catch (Exception e) {
                            }
                        }
                    } else {
                        try {
                            number = Integer.valueOf(item);
                            if (!tmpList.contains(number)) {
                                tmpList.add(number);
                            }
                        } catch (Exception e) {
                        }
                    }
                }
                result.put(entry.getKey(), tmpList.toArray(new Integer[]{}));
            }
        } else {
            result = null;
            syslogInfo("Not found master data config, programe is shutdown.");
            System.exit(1);
        }
        return result;
    }

    /**
     * 判断主接口与从接口是否是可转换的协议
     *
     * @param scp
     * @return
     */
    private static boolean masterCalibrationSlave(SlaveConnectParam scp) {
        boolean status;
        if (masterParam.isModbus()) {
            status = scp.isModbus();
        } else {
            status = !scp.isModbus();
        }
        return status;
    }

    /**
     * 读取ini配置文件
     */
    public static void initIniConfig() {
        String runtimeDirPath = SystemUtil.getRuntimeDirPath(ConverterApplication.class);
        String iniFilePath = runtimeDirPath + "converter.ini";
        File f = new File(iniFilePath);
        if (f.exists()) {
            try {
                ini = new Wini(f);
            } catch (IOException e) {
                logger.debug("Ini file configure fail.");
            }
        } else {
            try {
                f.createNewFile();
                ini = new Wini(f);
            } catch (Exception e) {
                logger.debug("Create ini file fail.");
            }
        }
    }

    /**
     * 获取log对象并动态设置日志级别
     *
     * @param clazz
     * @return
     */
    public static Logger getLogger(Class<?> clazz) {
        Logger logger = Logger.getLogger(clazz);
        logger.setLevel(Level.toLevel(ConverterApplication.commandCli.getArg("logLevel", "ERROR")));
        return logger;
    }

    /**
     * 控制台输出
     *
     * @param msg
     */
    public static void syslogInfo(String msg) {
        System.out.println(msg);
    }

    /**
     * 转换日志级别
     *
     * @return
     */
    public static Modbus.LogLevel getModbusLogLevel() {
        Modbus.LogLevel logLevel;
        Level level = logger.getLevel();
        switch (level.toInt()) {
            case 10000:
                logLevel = Modbus.LogLevel.LEVEL_DEBUG;
                break;
            case 20000:
                logLevel = Modbus.LogLevel.LEVEL_VERBOSE;
                break;
            case 30000:
                logLevel = Modbus.LogLevel.LEVEL_WARNINGS;
                break;
            default:
                logLevel = Modbus.LogLevel.LEVEL_RELEASE;
                break;
        }
        return logLevel;
    }
}
